package top.jfunc.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.*;
import java.util.Enumeration;
import java.util.regex.Pattern;

/**
 * @author xiongshiyan at 2020/6/16 , contact me with email yanshixiong@126.com or phone 15208384257
 */
public class IpUtil {
    private static final Logger logger = LoggerFactory.getLogger(IpUtil.class);
    /**
     * 获取用户真实IP地址，不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 可是，如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值
     * 获取代理之后的客户端的真实IP，支持多级代理
     * 1.有X-Forwarded-For的分两种：
     * 1.1有逗号分隔的，第一个才是真实IP
     * 1.2没有逗号分隔的，就是他
     * 2.有X-Real-IP的就是他
     * 3....
     *
     *
     X-Forwarded-For ：这是一个 Squid 开发的字段，只有在通过了HTTP代理或者负载均衡服务器时才会添加该项。　　　　
     　　　　格式为X-Forwarded-For:client1,proxy1,proxy2，一般情况下，第一个ip为客户端真实ip，后面的为经过的代理服务器ip。现在大部分的代理都会加上这个请求头。
     X-Real-IP  ：nginx代理一般会加上此请求头。
     Proxy-Client-IP/WL- Proxy-Client-IP ：这个一般是经过apache http服务器的请求才会有，用apache http做代理时一般会加上Proxy-Client-IP请求头，而WL-Proxy-Client-IP是他的weblogic插件加上的头。
     HTTP_CLIENT_IP ：有些代理服务器会加上此请求头。
     */
    private static final String UN_KNOWN           = "unknown";
    private static final String X_FORWARDED_FOR    = "X-FORWARDED-FOR";
    private static final String[] PROXY_IP_HEADERS = {"X-Real-IP" , "Proxy-Client-IP" , "WL-Proxy-Client-IP" , "HTTP_CLIENT_IP" , "HTTP_X_FORWARDED_FOR"};


    /**
     * 获取客户端真实IP
     * @param headerGetter 提供获取请求中header的方法
     * @param remoteAddressGetter 提供获取请求中远程地址的方法，可以为空，就忽略
     * @return 客户端IP
     */
    public static String getClientIp(HeaderGetter headerGetter , RemoteAddressGetter remoteAddressGetter) {
        String ip = headerGetter.getHeader(X_FORWARDED_FOR);
        if (StrUtil.isNotEmpty(ip) && !UN_KNOWN.equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if( ip.contains(StrUtil.COMMA) ){
                ip = ip.split(StrUtil.COMMA)[0];
            }
        }

        for (String proxyIpHeader : PROXY_IP_HEADERS) {
            if (StrUtil.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
                ip = headerGetter.getHeader(proxyIpHeader);
            }
        }

        if (StrUtil.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
            if(null != remoteAddressGetter){
                ip = remoteAddressGetter.getRemoteAddress();
            }
        }
        return ip;
    }
    @FunctionalInterface
    public interface RemoteAddressGetter {
        /**
         * 获取远程地址
         * @see HttpServletRequest
         */
        String getRemoteAddress();

    }



    public static final String ANY_HOST_VALUE = "0.0.0.0";
    public static final String LOCAL_HOST_VALUE = "127.0.0.1";
    public static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");


    /**
     * 缓存本地地址
     */
    private static volatile InetAddress LOCAL_ADDRESS = null;



    /**
     * Find first valid IP from local network card
     * @return first valid local IP
     */
    public static InetAddress getLocalAddress() {
        if (LOCAL_ADDRESS != null) {
            return LOCAL_ADDRESS;
        }
        InetAddress localAddress = getLocalAddress0();
        LOCAL_ADDRESS = localAddress;
        return localAddress;
    }
    /**
     * get ip address
     */
    public static String getLocalIp(){
        return getLocalAddress().getHostAddress();
    }

    private static InetAddress toValidAddress(InetAddress address) {
        if (address instanceof Inet6Address) {
            Inet6Address v6Address = (Inet6Address) address;
            if (isPreferIPV6Address()) {
                return normalizeV6Address(v6Address);
            }
        }
        if (isValidV4Address(address)) {
            return address;
        }
        return null;
    }

    /**
     * normalize the ipv6 Address, convert scope name to scope id.
     * e.g.
     * convert
     * fe80:0:0:0:894:aeec:f37d:23e1%en0
     * to
     * fe80:0:0:0:894:aeec:f37d:23e1%5
     * <p>
     * The %5 after ipv6 address is called scope id.
     * see java doc of {@link Inet6Address} for more details.
     *
     * @param address the input address
     * @return the normalized address, with scope id converted to int
     */
    private static InetAddress normalizeV6Address(Inet6Address address) {
        String addr = address.getHostAddress();
        int i = addr.lastIndexOf('%');
        if (i > 0) {
            try {
                return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
            } catch (UnknownHostException e) {
                // ignore
                logger.debug("Unknown IPV6 address: ", e);
            }
        }
        return address;
    }

    private static boolean isPreferIPV6Address() {
        return Boolean.getBoolean("java.net.preferIPv6Addresses");
    }

    /**
     * 检查是否是有效的ipv4地址
     */
    private static boolean isValidV4Address(InetAddress address) {
        if (address == null || address.isLoopbackAddress()) {
            return false;
        }
        String name = address.getHostAddress();
        return (name != null
                && IP_PATTERN.matcher(name).matches()
                && !ANY_HOST_VALUE.equals(name)
                && !LOCAL_HOST_VALUE.equals(name));
    }

    private static InetAddress getLocalAddress0() {
        InetAddress localAddress = null;
        try {
            localAddress = InetAddress.getLocalHost();
            InetAddress addressItem = toValidAddress(localAddress);
            if (addressItem != null) {
                return addressItem;
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }

        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            if (null == interfaces) {
                return localAddress;
            }
            while (interfaces.hasMoreElements()) {
                try {
                    NetworkInterface network = interfaces.nextElement();
                    if (network.isLoopback() || network.isVirtual() || !network.isUp()) {
                        continue;
                    }
                    Enumeration<InetAddress> addresses = network.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        try {
                            InetAddress addressItem = toValidAddress(addresses.nextElement());
                            if (addressItem != null) {
                                try {
                                    if(addressItem.isReachable(100)){
                                        return addressItem;
                                    }
                                } catch (IOException e) {
                                    // ignore
                                }
                            }
                        } catch (Throwable e) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                } catch (Throwable e) {
                    logger.error(e.getMessage(), e);
                }
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
        return localAddress;
    }


    /**
     * 找到可用的端口
     */
    public static int findAvailablePort(int defaultPort) {
        int portTmp = defaultPort;
        while (portTmp < 65535) {
            if (!isPortUsed(portTmp)) {
                return portTmp;
            } else {
                portTmp++;
            }
        }
        portTmp = defaultPort;
        while (portTmp > 0) {
            if (!isPortUsed(portTmp)) {
                return portTmp;
            } else {
                portTmp--;
            }
        }
        throw new RuntimeException("no available port.");
    }

    /**
     * 判断端口是否可用
     */
    public static boolean isPortUsed(int port) {
        try (ServerSocket serverSocket = new ServerSocket(port)){
            return false;
        } catch (IOException e) {
            return true;
        }
    }
}
